﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace davejk.Minecraft.ServerWrapper.Game
{
    public class MinecraftServer
    {

        public MinecraftServer()
        {
            _running = false;
        }

        #region Server control

        public void Start()
        {
            //Check we're not already running
            if (Running)
                throw new Exception("The server is already running.");

            //Check mandatory events
            if (Output == null)
                throw new Exception("The GameOutput event is not assigned.");
            if (StateChanged == null)
                throw new Exception("The StateChanged event is not assigned.");

            //Respawn the Process
            GameProcess = new Process();
            GameProcess.OutputDataReceived += new DataReceivedEventHandler(GameOutput);
            GameProcess.ErrorDataReceived += new DataReceivedEventHandler(GameOutput);
            GameProcess.Exited += new EventHandler(GameExited);

            //Reset the player list
            _players = new List<MinecraftPlayer>();

            //Set up Java
            GameProcess.StartInfo.WorkingDirectory = Utils.SetupHelper.AppBaseDirectory + "\\Minecraft";
            GameProcess.StartInfo.FileName = "java";
            GameProcess.StartInfo.Arguments = "-Xmx1024M -Xms1024M -jar minecraft_server.jar nogui";
            //TODO: Support for changing the Java arguments via config

            //TODO: Configure user to run server as
            //GameProcess.StartInfo.UserName
            //GameProcess.StartInfo.Password

            //Set up i/o redirection and events
            GameProcess.StartInfo.UseShellExecute = false;
            GameProcess.StartInfo.RedirectStandardInput = true;
            GameProcess.StartInfo.RedirectStandardOutput = true;
            GameProcess.StartInfo.RedirectStandardError = true;
            GameProcess.EnableRaisingEvents = true;
            GameProcess.StartInfo.ErrorDialog = false;

            //Hide the window if not debugging
#if !DEBUG
            GameProcess.StartInfo.CreateNoWindow = true;
#endif

            //Go hog wild!
            GameProcess.Start();

            GameProcess.BeginOutputReadLine();
            GameProcess.BeginErrorReadLine();

            _running = true;
            StateChanged(Running);

            Output("Server started");
        }

        public void Stop()
        {
            GameProcess.StandardInput.WriteLine("stop");
        }
        public void ForceStop()
        {
            if (!Running)
                return;
            try
            {
                GameProcess.StandardInput.WriteLine("stop");
            }
            catch { }
            GameProcess.WaitForExit(20000);
            try
            {
                GameProcess.Kill();
                _running = false;
                StateChanged(Running);
            }
            catch { }
        }
        public void WaitForStop(int timeout = -1)
        {
            Stop();
            if (timeout > 0)
                GameProcess.WaitForExit(timeout);
            else
                GameProcess.WaitForExit();
        }

        public void Restart()
        {
            ForceStop();
            if (!Running)
                Start();
            else
                throw new Exception("Unable to shut down the server.");
        }

        public void SendInput(string input)
        {
            if(!String.IsNullOrEmpty(input))
                GameProcess.StandardInput.WriteLine(input);
        }

        public void SendInputSet(string[] input)
        {
            foreach (string line in input)
                SendInput(line);
        }

        #endregion

        #region Game process events

        private void GameOutput(object sender, DataReceivedEventArgs e)
        {
            lock (this)
            {
                if (!String.IsNullOrEmpty(e.Data))
                {
                    string msg = e.Data;

                    //todo: logging support

                    //Strip the date and time from messages containing it
                    if (msg.IndexOf("[INFO]") != -1)
                    {
                        msg = msg.Substring(msg.IndexOf("[INFO]"));
                    }
                    else if (msg.IndexOf("[WARNING]") != -1)
                    {
                        msg = msg.Substring(msg.IndexOf("[WARNING]"));
                    }

                    if (msg.StartsWith("[INFO] <")) 
                    {   //Chat message
                        msg = msg.Substring("[INFO] <".Length);
                        string pname = msg.Substring(0, msg.IndexOf('>')).ToLower();
                        string chat = msg.Substring(msg.IndexOf('>') + 2).ToLower();

                        //See if it's a wrapper command
                        UserCommands.ServerCommandType CommandType = UserCommands.GetCommandType(chat);
                        if(CommandType == UserCommands.ServerCommandType.GiveItem) {
                            if((Utils.Ops.IsOp(pname) || AllowGive)) {
                                List<string> names = new List<string>();
                                foreach(MinecraftPlayer player in Players)
                                    names.Add(player.Name.ToLower());
                                SendInputSet(UserCommands.Run(pname, chat, names));
                            } else {
                                SendInput("tell " + pname + " The server has disabled that command.");
                            }
                            Chat(pname, chat);
                        }
                        else if (CommandType == UserCommands.ServerCommandType.Teleport)
                        {
                            if ((Utils.Ops.IsOp(pname) || AllowGoto))
                            {
                                List<string> names = new List<string>();
                                foreach (MinecraftPlayer player in Players)
                                    names.Add(player.Name.ToLower());
                                SendInputSet(UserCommands.Run(pname, chat, names));
                            }
                            else
                            {
                                SendInput("tell " + pname + " The server has disabled that command.");
                            }
                            Chat(pname, chat);
                        }
                        else
                        {
                            if (Chat != null)
                                Chat(pname, chat);
                            else
                                Output("<" + pname + "> " + chat);
                        }
                    }
                    else if (msg.StartsWith("[INFO]") && msg.EndsWith("logged in")) 
                    {   //Player connecting
                        msg = msg.Substring("[INFO] ".Length);
                        string pname = msg.Substring(0, msg.IndexOf('[') - 1);
                        msg = msg.Substring(msg.IndexOf('[') + 2);
                        string ip = msg.Substring(0, msg.IndexOf(':'));
                        msg = msg.Substring(msg.IndexOf(':') + 1);
                        int port = Int32.Parse(msg.Substring(0, msg.IndexOf(']')));
                        MinecraftPlayer NewPlayer = new MinecraftPlayer(pname, ip, port);
                        _players.Add(NewPlayer);
                        if (PlayerJoined != null)
                            PlayerJoined(NewPlayer, _players.Count);
                        else
                            Output(NewPlayer.Name + " joined from " + NewPlayer.Ip + ":" +
                                NewPlayer.Port.ToString());
                    }
                    else if (msg.StartsWith("[INFO]") && msg.IndexOf("lost connection") != -1) 
                    {   //Player leaving
                        msg = msg.Substring("[INFO] ".Length);
                        string pname = msg.Substring(0, msg.IndexOf("lost connection") - 1);
                        msg = msg.Substring(msg.IndexOf("lost connection") + "lost connection: ".Length);
                        string reason = msg;

                        //todo: use linq for this instead
                        foreach (MinecraftPlayer p in _players)
                        {
                            if (p.Name == pname)
                            {
                                _players.Remove(p);
                                break;
                            }
                        }
                        PlayerLeft(pname, _players.Count, reason);
                    }
                    else if (msg.StartsWith("Player count: "))
                    {
                        //TODO: Use this to sanity check the length of _players
                    }
                    else if (msg.StartsWith("Preparing spawn area: ") && MapGenProgress != null)
                    {   //Map gen
                        msg = msg.Substring("Preparing spawn area: ".Length);
                        MapGenProgress(Int32.Parse(msg.Substring(0, msg.IndexOf('%'))));
                    }
                    else
                    {
                        Output(msg);
                    }
                }
            }
        }
        void GameExited(object sender, EventArgs e)
        {
            _running = false;
            StateChanged(Running);

            Output("Server stopped.");

            return;
        }

        #endregion

        #region Game server events

        //All the events that include a MinecraftPlayer should use LINQ to look up a
        //player from List<MinecraftPlayer> _players, since it contains more info than
        //just the username you get from the console output

        public delegate void MapGenProgressHandler(int percentage);
        public event MapGenProgressHandler MapGenProgress;

        public delegate void PlayerJoinedHandler(MinecraftPlayer Player, int PlayerCount);
        public event PlayerJoinedHandler PlayerJoined;

        public delegate void PlayerLeftHandler(string Name, int PlayerCount, string Reason);
        public event PlayerLeftHandler PlayerLeft;

        //TODO: Raise event when an op uses RCON
        public delegate void OpCommandHandler(MinecraftPlayer Player, string Command);
        public event OpCommandHandler OpCommand;

        public delegate void ChatHandler(string Name, string Message);
        public event ChatHandler Chat;

        public delegate void OutputHandler(string output);
        public event OutputHandler Output;

        public delegate void StateChangedHandler(bool running);
        public event StateChangedHandler StateChanged;

        #endregion

        #region Properties

        private List<MinecraftPlayer> _players;
        public List<MinecraftPlayer> Players
        {
            get
            {
                return _players;
            }
        }

        private bool _running;
        public bool Running {
            get
            {
                return _running;
            }
        }

        private bool _allowGive;
        public bool AllowGive
        {
            get
            {
                return _allowGive;
            }
            set
            {
                _allowGive = value;
            }
        }

        private bool _allowGoto;
        public bool AllowGoto
        {
            get
            {
                return _allowGoto;
            }
            set
            {
                _allowGoto = value;
            }
        }

        #endregion

        private Process GameProcess;

    }
}
